package com.example.springDemo;

import ch.qos.logback.classic.db.DBHelper;
import com.example.springDemo.bean.Pet;
import com.example.springDemo.bean.User;
import com.example.springDemo.config.MyConfig;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.ComponentScan;

/**
 * 主程序类
 * ● 默认的包结构
 *   ○ 主程序所在包及其下面的所有子包里面的组件都会被默认扫描进来
 *   ○ 无需以前的包扫描配置
 *   ○ 想要改变扫描路径，
 *   @SpringBootApplication(scanBasePackages="com.example")
 *   或者@ComponentScan("com.example") 指定扫描路径
 * 1.@EnableAutoConfiguration//默认加载了所有类  127个
 * 虽然我们127个场景的所有自动配置启动的时候默认全部加载。xxxxAutoConfiguration
 * 按照条件装配规则（@Conditional），最终会按需配置。
 *
 * 自动配置
 * 总结：
 * ● SpringBoot先加载所有的自动配置类  XxxAutoConfiguration
 * ● 每个自动配置类按照条件进行生效，默认都会绑定配置文件指定的值。XxxProperties里面拿。xxxProperties和配置文件进行了绑定
 * ● 生效的配置类就会给容器中装配很多组件
 * ● 只要容器中有这些组件，相当于这些功能就有了
 * ● 定制化配置
 *   ○ 用户直接自己@Bean替换底层的组件
 *   ○ 用户去看这个组件是获取的配置文件什么值就去修改。
 * XxxAutoConfiguration ---> 组件  ---> XxxProperties里面拿值  ----> application.properties
 * Spring 是一个“引擎”;
 * Spring MVC 是基于Spring的一个 MVC 框架;
 * Spring Boot 是基于Spring4的条件注册的一套快速开发整合包。
 */
// @SpringBootApplication(scanBasePackages = "com.example") //这一个等一下面三个
@EnableAutoConfiguration(/*exclude = RedisAutoConfiguration.class禁用掉RedisAutoConfiguration自动配置*/)//默认加载了所有类  127个//开启自动配置
    /*
     * @EnableAutoConfiguration相当于👇
     *      @AutoConfigurationPackage相当于👇
     *          @Import({Registrar.class})导入一系列组件
     *              /利用Registrar给容器中导入一系列组件
     *              //将指定的一个包下的所有组件导入进来？SpringDemoApplication 所在包下。
     *       @Import({AutoConfigurationImportSelector.class})
     *          AutoConfigurationImportSelector.class
     *              批量
     *
     *  1、利用getAutoConfigurationEntry(annotationMetadata);给容器中批量导入一些组件
     *  2、调用List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes)获取到所有需要导入到容器中的配置类
     *  3、利用工厂加载 Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader)；得到所有的组件
     *  4、从META-INF/spring.factories位置来加载一个文件。
     *  默认扫描我们当前系统里面所有META-INF/spring.factories位置的文件
     *  spring-boot-autoconfigure-2.5.5.jar包里面也有META-INF/spring.factories
     * # Auto Configure下面
     * org.springframework.boot.autoconfigure.EnableAutoConfiguration
     * 里面写死了一百多个刚开始的自动配置  已启动就启动
     * 虽然我们127个场景的所有自动配置启动的时候默认全部加载。xxxxAutoConfiguration
     *  按照条件装配规则（@ConditionalOnXxx），最终会按需配置。
     *
     */

@SpringBootConfiguration
   /*
    * @SpringBootConfiguration 相当于👇
    * @Target({ElementType.TYPE})
    * @Retention(RetentionPolicy.RUNTIME)
    * @Documented
    * @Configuration//表示这是一个配置类
    * @Indexed
    * */
@ComponentScan("com.example.MVC")//spring MVC测试
//@ComponentScan("com.example.springDemo")//指定扫描那些Spring注解
//@ComponentScan("com.example.spring5.Spring5新特性.Webflux新功能.注解的编程模型")//spring5测试
//@ComponentScan("com.example.spring5.Spring5新特性.Webflux新功能.函数式编程模型")//spring5测试
//@ServletComponentScan(basePackages = "com.example.springDemo")//Servlet扫描 走过滤器不走拦截器
//@MapperScan("com.example.springDemo.mapper")//可以批量加入@Mapper注解
public class SpringDemoApplication {


    public static void main(String[] args) {
//        返回一个ioc容器
        ConfigurableApplicationContext run = SpringApplication.run(SpringDemoApplication.class, args);
//        Arrays.stream(run.getBeanDefinitionNames()).forEach(System.out::println);
//        System.out.println(run.getBeanDefinitionCount());//加载了多少各类

//        组件获取(run);
//        老组件(run);
//        System.out.println(run.containsBean("mycar"));//判断是否与application.yml的mycar一样


    }


    private static void 老组件Bean(ConfigurableApplicationContext run) {
        boolean tom = run.containsBean("pet01");//判断是否有pet01这个组件
        boolean user01 = run.containsBean("user01");
        System.out.println("user01组件"+user01);
//        System.out.println("tomcatPet组件"+run.containsBean("tomcatPet"));

        System.out.println(run.containsBean("pet0001"));
        System.out.println(run.containsBean("user0001"));
    }

    private static void 组件获取Bean(ConfigurableApplicationContext run) {
        //从容器中获取组件
        Pet pet01 = run.getBean("pet01", Pet.class);
        Pet pet02 = run.getBean("pet01", Pet.class);
        System.out.println("组件" + (pet01 == pet02));//true默认是单例的


        /**
         * MyConfig这个类加注释
         *  @Configuration(proxyBeanMethods = true)//代理对象调用方法（默认的）
         *         SpringBoot总会检查这个组件是否在容器中有  保持单实例
         *  @Configuration(proxyBeanMethods = false)   不保持单实例
         *      这是一个配置类 == 配置文件告诉spring boot这是一个配置类
         */
        //com.example.springDemo.controller.config.MyConfig$$EnhancerBySpringCGLIB$$20647f77@5a865416
        MyConfig bean = run.getBean(MyConfig.class);
        System.out.println(bean);
        User user0 = bean.user01();
        User user1 = bean.user01();
        System.out.println(user0 == user1);//true

        User user01 = run.getBean("user01", User.class);
        Pet tom = run.getBean("pet01", Pet.class);
        System.out.println("用户的宠物" + (user01.getPet() == tom));
        //@Configuration(proxyBeanMethods = true)     true   因为是单实例
        //proxyBeanMethods =  false  因为不是单实例 每一次new  就会有新的 地址不同

        System.out.println("======================");
        String[] beanNamesForType = run.getBeanNamesForType(User.class);
        for (String s:beanNamesForType) {
            System.out.println(s);
        }
        System.out.println(run.getBean(DBHelper.class));
    }
}
